package com.jzo2o.market.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.*;
import com.jzo2o.market.constants.RedisConstants;
import com.jzo2o.market.constants.TabTypeConstants;
import com.jzo2o.market.enums.ActivityStatusEnum;
import com.jzo2o.market.enums.CouponStatusEnum;
import com.jzo2o.market.mapper.ActivityMapper;
import com.jzo2o.market.model.domain.Activity;
import com.jzo2o.market.model.domain.Coupon;
import com.jzo2o.market.model.domain.CouponWriteOff;
import com.jzo2o.market.model.dto.request.ActivityQueryForPageReqDTO;
import com.jzo2o.market.model.dto.request.ActivitySaveReqDTO;
import com.jzo2o.market.model.dto.response.ActivityInfoResDTO;
import com.jzo2o.market.service.IActivityService;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.market.service.ICouponWriteOffService;
import com.jzo2o.mysql.utils.PageUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-09-16
 */
@Service
public class ActivityServiceImpl extends ServiceImpl<ActivityMapper, Activity> implements IActivityService {
    private static final int MILLION = 1000000;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private ICouponService couponService;

    @Resource
    private ICouponWriteOffService couponWriteOffService;

    @Override
    public PageResult<ActivityInfoResDTO> pageQuery(ActivityQueryForPageReqDTO dto) {
        Page<Activity> page = PageUtils.parsePageQuery(dto, Activity.class);
        Page<Activity> selectPage = baseMapper.selectPage(page, new QueryWrapper<>());
        return PageUtils.toPage(selectPage, ActivityInfoResDTO.class);
    }

    @Override
    public void saveCoupon(ActivitySaveReqDTO dto) {
        // 校验
        dto.check();

        // 发放开始时间不能小于当前时间
        if(dto.getDistributeStartTime().isBefore(DateUtils.now())){
            throw new ForbiddenOperationException("发放开始时间不能小于当前时间");
        }

        Activity activity = BeanUtil.toBean(dto, Activity.class);

        activity.setStatus(ActivityStatusEnum.NO_DISTRIBUTE.getStatus());
        activity.setStockNum(dto.getTotalNum());

        if(!ObjectUtil.isNull(dto.getId())){
            this.updateById(activity);
        }else {
            save(activity);
        }
    }

    @Override
    public ActivityInfoResDTO getByCouponId(Long id) {
        Activity activity = this.getById(id);
        if(ObjectUtil.isNull(activity)){
            throw new ForbiddenOperationException("该活动不存在");
        }

        ActivityInfoResDTO activityInfoResDTO = BeanUtil.toBean(activity, ActivityInfoResDTO.class);

        activityInfoResDTO.setReceiveNum(activity.getTotalNum() - activity.getStockNum());

        List<CouponWriteOff> list = couponWriteOffService.lambdaQuery()
                .eq(CouponWriteOff::getActivityId, id)
                .list();

        activityInfoResDTO.setWriteOffNum(list.size());

        return activityInfoResDTO;
    }

    @Override
    public void revoke(Long id) {

        Activity activity = this.getById(id);
        if(ObjectUtil.isNull(activity)){
            throw new ForbiddenOperationException("该活动不存在");
        }

        List<Integer> list = Arrays.asList(ActivityStatusEnum.VOIDED.getStatus(), ActivityStatusEnum.LOSE_EFFICACY.getStatus());

        if(list.contains(activity.getStatus())){
            throw new ForbiddenOperationException("该活动不可操作");
        }

        // 更新活动已失效
        this.lambdaUpdate()
                .eq(Activity::getId, id)
                .set(Activity::getStatus, ActivityStatusEnum.VOIDED.getStatus())
                .update();

        // 更新coupon表已失效
        couponService.lambdaUpdate()
                .eq(Coupon::getActivityId, id)
                .set(Coupon::getStatus, CouponStatusEnum.INVALID.getStatus())
                .update();

    }

    /**
     * 活动缓存处理
     */
    @Override
    public void preheat() {
        //1.查询活动列表（30天内进行中的和待开始的），结果根据活动开始时间倒序
        List<Activity> list = this.lambdaQuery()
                .in(Activity::getStatus, ActivityStatusEnum.DISTRIBUTING.getStatus(), ActivityStatusEnum.NO_DISTRIBUTE.getStatus())
                .lt(Activity::getDistributeStartTime, DateUtils.now().plusDays(30))
                .orderByDesc(Activity::getDistributeStartTime)
                .list();

        if(CollUtils.isEmpty(list)){
            list = new ArrayList<>();
        }

        List<ActivityInfoResDTO> activityInfoResDTOS = BeanUtil.copyToList(list, ActivityInfoResDTO.class);
        String jsonStr = JsonUtils.toJsonStr(activityInfoResDTOS);

        //4.保存到Redis缓存中（永不过期）
        redisTemplate.opsForValue().set(RedisConstants.RedisKey.ACTIVITY_CACHE_LIST, jsonStr);

        activityInfoResDTOS.stream().filter(x-> ObjectUtil.equal(
                ActivityStatusEnum.DISTRIBUTING.getStatus(),
                getCurrentRealStatus(x))
        ).forEach(x->{
            String redisKey = String.format(RedisConstants.RedisKey.COUPON_RESOURCE_STOCK, x.getId() % 10);
            Object hashKey = x.getId(); //活动ID
            Object value = x.getStockNum();//库存数量
            redisTemplate.opsForHash().put(redisKey, hashKey, value);
        });

        activityInfoResDTOS.stream().filter(
                x->ObjectUtils.equal(ActivityStatusEnum.DISTRIBUTING.getStatus(), getCurrentRealStatus(x))
        ).forEach(
                x->{
                    String redisKey = String.format(RedisConstants.RedisKey.COUPON_RESOURCE_STOCK, x.getId() % 10);
                    Object hashKey = x.getId(); //活动ID
                    Object value = x.getStockNum();//库存数量
                    redisTemplate.opsForHash().putIfAbsent(redisKey, hashKey, value);
                }
        );
    }

    @Override
    public List<ActivityInfoResDTO> queryForPage(Integer tabType) {

        Object jsonStr = redisTemplate.opsForValue().get(RedisConstants.RedisKey.ACTIVITY_CACHE_LIST);

        List<ActivityInfoResDTO> activityInfoResDTOS = JsonUtils.toList(jsonStr.toString(), ActivityInfoResDTO.class);

        // 传进来的状态，转成数据库的状态
        Integer status = ObjectUtils.equal(tabType, TabTypeConstants.SEIZING) ? ActivityStatusEnum.DISTRIBUTING.getStatus() : ActivityStatusEnum.NO_DISTRIBUTE.getStatus();

        List<ActivityInfoResDTO> collect = activityInfoResDTOS.stream()
                // 过滤 活动的状态 == 传过来的状态
                .filter(a -> ObjectUtils.equal(status, getCurrentRealStatus(a)))
                .peek(a -> {
                    a.setRemainNum(a.getStockNum());
                    a.setStatus(status);
                })
                .collect(Collectors.toList());

        return collect;
    }

    /**
     * 判断真实状态
     * @param a
     * @return
     */
    public Integer getCurrentRealStatus(ActivityInfoResDTO a) {
        Integer originStatus = a.getStatus();
        LocalDateTime startTime = a.getDistributeStartTime();
        LocalDateTime endTime = a.getDistributeEndTime();

        if(ObjectUtil.equal(originStatus, ActivityStatusEnum.NO_DISTRIBUTE.getStatus())
                && startTime.isBefore(DateUtils.now()) && endTime.isAfter(DateUtils.now())){
            return ActivityStatusEnum.DISTRIBUTING.getStatus();
        }else if (ObjectUtil.equal(originStatus, ActivityStatusEnum.NO_DISTRIBUTE.getStatus())
                && endTime.isBefore(DateUtils.now())){
            return ActivityStatusEnum.LOSE_EFFICACY.getStatus();
        }else if (ObjectUtils.equals(originStatus, ActivityStatusEnum.DISTRIBUTING.getStatus())
                && endTime.isBefore(DateUtils.now())){
            return ActivityStatusEnum.LOSE_EFFICACY.getStatus();
        }

        return originStatus;
    }

    @Override
    public boolean decrementStockNum(Long activityId) {
        // update activity set stock_num = stock_num - 1 where id = activityId and
        boolean result = this.lambdaUpdate()
                .setSql(" stock_num = stock_num - 1 ")
                .eq(Activity::getId, activityId)
                .gt(Activity::getStockNum, 0)
                .update();

        return result;
    }
}
